import { Arrays, Numbers } from '../../../../../wing/assets/src';
import { modifier } from '../../decorators';
import { LoggerService } from '../LoggerComponent';
import { RichTextStyle } from './RichTextStyle';

/**
 * - 作者: DoooReyn
 * - 日期: 2023.11.17
 * - 名称: RichTextParser
 * - 描述: HTML 超级富文本解析器
 */
@modifier('RichTextParser')
export class RichTextParser extends LoggerService {
    /** 文本注释样式 */
    static readonly CommentStyle = ['none', 'underline', 'waveline', 'highlight', 'strikeout', 'rect', 'roundrect'];

    /** 垂直方向对齐方式 */
    static readonly Alignment = ['top', 'bottom', 'center'];

    /** 整体样式 */
    style: RichTextStyle;

    /** 所有子节点 */
    children: any[];

    /** 出现错误 */
    private _error: boolean;
    get error() {
        return this._error;
    }

    /** 画布 */
    private canvas: HTMLCanvasElement;

    /** 画布渲染上下文 */
    private ctx: CanvasRenderingContext2D;

    constructor() {
        super();
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d')!;
        this.style = {} as RichTextStyle;
        this.children = [];
    }

    /**
     * 解析富文本
     * @param content 富文本内容
     * @returns
     */
    parse(content: string) {
        this.children.length = 0;
        this._error = false;

        const parser = new DOMParser();
        const doc = parser.parseFromString(content, 'application/xml');
        const error = doc.querySelector('parsererror');
        if (error) {
            this._error = true;
            this.logger.error('富文本解析错误');
            return;
        }

        this.parseRoot(doc.documentElement);
        doc.documentElement.childNodes.forEach((v) => this.parseChild(v));
    }

    /**
     * 解析富文本根节点（整体样式）
     * @param node 根节点
     */
    private parseRoot(node: Element) {
        this.style.maxWidth = this.parseAttribute(node, 'maxWidth', 100);
        this.style.margin = this.parseAttribute(node, 'margin', [0, 0, 0, 0]);
        this.style.space = this.parseAttribute(node, 'space', [0, 0]);
        this.style.align = this.parseAttribute(node, 'align', 'bottom');
        this.style.font = this.parseAttribute(node, 'font', '14px Arial');
        this.style.rowHeight = this.parseAttribute(node, 'rowHeight', 16);
        this.style.lineWeight = this.parseAttribute(node, 'lineWeight', 2);
        this.style.background = this.parseAttribute(node, 'background', '#00000000');
        this.style.fillColor = this.parseAttribute(node, 'fillColor', '#000');
        this.style.strokeColor = this.parseAttribute(node, 'strokeColor', '#000');

        this.ctx.font = this.style.font;
        this.ctx.fillStyle = this.style.fillColor;
        this.ctx.strokeStyle = this.style.strokeColor;
        this.ctx.lineWidth = this.style.lineWeight;
    }

    /**
     * 解析富文本子节点（子节点样式）
     * @param node 子节点
     */
    private parseChild(node: ChildNode) {
        if (node instanceof Element) {
            switch (node.nodeName) {
                case 'newline':
                    this.children.push({ type: 'newline', size: [0, this.style.rowHeight] });
                    break;
                case 'blankline':
                    this.children.push({ type: 'blankline', size: [this.style.maxWidth, this.style.rowHeight] });
                    break;
                case 'text':
                    const text = this.parseAttribute(node, 'value', undefined);
                    if (text && text.length > 0) {
                        const font = this.parseAttribute(node, 'font', this.style.font);
                        const color = this.parseAttribute(node, 'color', this.style.fillColor);
                        const outline = this.parseAttribute(node, 'outline', [0, '#fff']);
                        const shadow = this.parseAttribute(node, 'shadow', [0, 0, 0, '#fff']);
                        const comment = this.parseAttribute(node, 'comment', ['none', '#f00', 2]);
                        (text as string).split(/(?=.)/su).forEach((v) => {
                            this.ctx.font = font;
                            this.ctx.beginPath();
                            if (outline[0] > 0) {
                                this.ctx.lineWidth = outline[0];
                                this.ctx.strokeStyle = color;
                            }
                            this.ctx.strokeStyle = outline[1];
                            this.ctx.shadowOffsetX = shadow[0];
                            this.ctx.shadowOffsetY = shadow[1];
                            this.ctx.shadowBlur = shadow[2];
                            this.ctx.shadowColor = shadow[3];
                            const metrics = this.ctx.measureText(v);
                            const width = Math.max(
                                metrics.width,
                                metrics.actualBoundingBoxLeft + metrics.actualBoundingBoxRight
                            );
                            const height = metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent;
                            this.children.push({
                                type: 'text',
                                row: 0,
                                font,
                                color,
                                shadow,
                                outline,
                                comment,
                                text: v,
                                size: [width, height],
                                metrics,
                            });
                        });
                    }
                    break;
                case 'image':
                    const uuid = this.parseAttribute(node, 'uuid', undefined);
                    let size = this.parseAttribute(node, 'size', [0, 0]);
                    if (uuid && uuid.length > 0 && (size as number[]).every((v) => v > 0)) {
                        let child = { type: 'image', row: 0, uuid, size };
                        this.children.push(child);
                    }
                    break;
                case 'outer':
                    const outerSize = this.parseAttribute(node, 'size', [0, 0]);
                    if ((outerSize as number[]).every((v) => v > 0)) {
                        const callback = node.getAttribute('callback');
                        this.children.push({ type: 'outer', row: 0, size: outerSize, callback });
                    }
                    break;
            }
        }
    }

    /**
     * 解析节点属性
     * @param node 节点
     * @param attr 属性
     * @param defaultValue 默认值
     * @returns
     */
    private parseAttribute(node: Element, attr: string, defaultValue: any) {
        let attrValue = node.getAttribute(attr);
        if (attrValue === null) return defaultValue;

        let value = defaultValue;
        switch (attr) {
            case 'maxWidth':
            case 'rowHeight':
                value = Numbers.toInteger(attrValue, defaultValue);
                break;
            case 'margin':
                const margin = attrValue.split(',').map((v) => Numbers.toInteger(v, 0));
                value = Arrays.fill(margin, 4, 0);
                break;
            case 'align':
                if (Arrays.contains(RichTextParser.Alignment, attrValue)) value = attrValue;
                break;
            case 'space':
                const space = attrValue.split(',').map((v) => Numbers.toInteger(v, 0));
                value = Arrays.fill(space, 2, 0);
                break;
            case 'shadow':
                let [_offsetX, _offsetY, _blur, _shadowColor] = attrValue.split(',');
                let offsetX = Numbers.toInteger(_offsetX, defaultValue[0]);
                let offsetY = Numbers.toInteger(_offsetY, defaultValue[1]);
                let blur = Numbers.toInteger(_blur, defaultValue[2]);
                let shadowColor = _shadowColor ?? defaultValue[3];
                value = [offsetX, offsetY, blur, shadowColor];
                break;
            case 'comment':
                let [_style, _color, _weight] = attrValue.split(',');
                let style = _style ?? defaultValue[0];
                let color = _color ?? defaultValue[1];
                let weight = Numbers.toInteger(_weight, defaultValue[2]);
                if (!Arrays.contains(RichTextParser.CommentStyle, style)) {
                    style = 'none';
                }
                value = [style, color, weight];
                break;
            case 'size':
                const size = attrValue.split(',').map((v, i) => Numbers.toInteger(v, defaultValue[i]));
                value = Arrays.fill(size, 2, 0);
                break;
            case 'outline':
                let [outlineWidth, outlineColor] = attrValue.split(',');
                outlineWidth = outlineWidth ?? defaultValue[0];
                outlineColor = outlineColor ?? defaultValue[1];
                value = [outlineWidth, outlineColor];
                break;
            default:
                value = attrValue;
                break;
        }

        return value;
    }
}
